<?php
/**
 * @package es2reveng
 * @name ES2 .VOL unpacker
 * @author marco 'harddisk' schuster
 * @license GPL v3
 */
require 'helpers.php';
$f = $argv[1];
echo "viewing file $f\n";
if (!is_file($f))
	die("file broken");
if (isset($argv[2]) && $argv[2] != "") {
	$unpackdir = realpath($argv[2])."/".basename($f);
	$res = @mkdir($unpackdir);
	if ($res === false)
		echo "unpacking: mkdir returned error code (possible reason: directory exists)\n";
	$unpack = true;
	echo "unpacking into $unpackdir\n";
} else
	$unpack = false;

$fp = fopen($f, "rb");

//check magic bytes
$offset = -1; //the ++ incrementor will read at byte 1 if this is omitted
$magic = getchar($fp, ++$offset);
$magic .= getchar($fp, ++$offset);
$magic .= getchar($fp, ++$offset);
$magic .= getchar($fp, ++$offset);
if ($magic != "VOLN")
	die("wrong magic code. this is not a valid VOL file!\n");

//output size
echo "container file size: 0x".dechex(filesize($f))."\n";

//check DBSIM usage bit
$sim = getchar($fp, ++$offset, false);
if ($sim == 1)
	echo "this file is used by DBSIM.EXE\n";
elseif ($sim == 0)
	echo "this file is not used by DBSIM.EXE\n";
else
	echo "warning: strange value for the SIM byte\n";

//check VSHELL.EXE usage bit
$sim = getchar($fp, ++$offset, false);
if ($sim == 1)
	echo "this file is used by VSHELL.EXE\n";
elseif ($sim == 0)
	echo "this file is not used by VSHELL.EXE\n";
else
	echo "warning: strange value for the SHELL byte\n";

//check the RESERVED1/RESERVED2 bytes
if (getchar($fp, ++$offset, false) != 0)
	echo "warning: strange value for the RESERVED1 byte\n";
if (getchar($fp, ++$offset, false) != 0)
	echo "warning: strange value for the RESERVED2 byte\n";

//get the UNKNOWN1 byte
$unk1 = getchar($fp, ++$offset, false);
echo "UNKNOWN1 is $unk1\n";

//get the directory count
$dircnt = getchar($fp, ++$offset, false);
echo "this file has $dircnt directories\n";

//get the directory list
//get length
$ll = getchar($fp, ++$offset, false);
$lh = getchar($fp, ++$offset, false);
$len = le_to_int($ll, $lh);
//get the list itself
$buf = "";
for ($i = 0; $i < $len; $i++)
	$buf .= getchar($fp, ++$offset);
//split it
$dirs = explode("\x00", $buf);
//clean it up (trim names)
$dirs_new = array();
foreach ($dirs as $i => $d) {
	$d = trim($d);
	if (strlen($d) > 0) {
		$dirs_new[$i] = trim($d);
		$res = @mkdir($unpackdir."/".$dirs_new[$i]);
		if ($res === false)
			echo "warning: mkdir returned error code (possible reason: directory exists) for '".$unpackdir."/".$dirs_new[$i]."'\n";
	}
}
$dirs = $dirs_new;
echo "directory list:\n".print_r($dirs, true);

//get the file list
//get the file count
$cl = getchar($fp, ++$offset, false);
$ch = getchar($fp, ++$offset, false);
$fcnt = le_to_int($cl, $ch);
echo "total file count (header): $fcnt files\n";
//get string length
$ll = getchar($fp, ++$offset, false);
$lh = getchar($fp, ++$offset, false);
$len = le_to_int($ll, $lh);
//get the UNKNOWN4 byte
$unk4 = getchar($fp, ++$offset, false);
echo "UNKNOWN4 is $unk4\n";
//get the UNKNOWN5 byte
$unk5 = getchar($fp, ++$offset, false);
echo "UNKNOWN5 is $unk5\n";
//get the list itself
$buf = "";
for ($i = 0; $i < $len; $i++)
	$buf .= getchar($fp, ++$offset);
//echo "file list: '$buf'\n";
//parse it
$current_file = "";
$files = array();
for ($i = 0; $i < strlen($buf); $i++) {
	$c = substr($buf, $i, 1);
	//	echo "read byte at position $i: ".dechex(ord($c))." ($c)\n";
	if (ord($c) == 0) { //filename terminated => read the following 5 bytes
		$parms_len = 18 - strlen($current_file); //fname+info are always padded to 18 chars
		$parms = substr($buf, $i + 1, $parms_len - 1); //adjust the offset and length to avoid reading the termination nullbyte of the file name
		//		echo "encountered a nullbyte, splitting '$current_file' off. parms_len is $parms_len, parms is '$parms'\n";
		$files[] = array("name" => $current_file, "parms" => $parms);
		$current_file = "";
		$i = $i + $parms_len - 1;
	} else {
		$current_file .= $c;
	}
}
//load the file parameters
foreach ($files as $id => $current_file) {
	//get the header bytes
	echo "file '".$current_file["name"]."', paramlen ".strlen($current_file["parms"])."\n";
	switch (strlen($current_file["parms"])) {
	case 5:
		$current_file["ignored"] = "";
		$current_file["dir"] = ord(substr($current_file["parms"], 0, 1));
		$current_file["firstbyte"] = le_to_int(ord(substr($current_file["parms"], 1, 1)), ord(substr($current_file["parms"], 2, 1)), ord(substr($current_file["parms"], 3, 1)), ord(substr($current_file["parms"], 4, 1)));
		break;
	case 6:
		$current_file["ignored"] = substr($current_file["parms"], 0, 1);
		$current_file["dir"] = ord(substr($current_file["parms"], 1, 1));
		$current_file["firstbyte"] = le_to_int(ord(substr($current_file["parms"], 2, 1)), ord(substr($current_file["parms"], 3, 1)), ord(substr($current_file["parms"], 4, 1)), ord(substr($current_file["parms"], 5, 1)));
		break;
	case 7:
		$current_file["ignored"] = substr($current_file["parms"], 0, 2);
		$current_file["dir"] = ord(substr($current_file["parms"], 2, 1));
		$current_file["firstbyte"] = le_to_int(ord(substr($current_file["parms"], 3, 1)), ord(substr($current_file["parms"], 4, 1)), ord(substr($current_file["parms"], 5, 1)), ord(substr($current_file["parms"], 6, 1)));
		break;
	case 8:
		$current_file["ignored"] = substr($current_file["parms"], 0, 3);
		$current_file["dir"] = ord(substr($current_file["parms"], 3, 1));
		$current_file["firstbyte"] = le_to_int(ord(substr($current_file["parms"], 4, 1)), ord(substr($current_file["parms"], 5, 1)), ord(substr($current_file["parms"], 6, 1)), ord(substr($current_file["parms"], 7, 1)));
		break;
	case 9:
		$current_file["ignored"] = substr($current_file["parms"], 0, 4);
		$current_file["dir"] = ord(substr($current_file["parms"], 4, 1));
		$current_file["firstbyte"] = le_to_int(ord(substr($current_file["parms"], 5, 1)), ord(substr($current_file["parms"], 6, 1)), ord(substr($current_file["parms"], 7, 1)), ord(substr($current_file["parms"], 8, 1)));
		break;
	case 10:
		$current_file["ignored"] = substr($current_file["parms"], 0, 5);
		$current_file["dir"] = ord(substr($current_file["parms"], 5, 1));
		$current_file["firstbyte"] = le_to_int(ord(substr($current_file["parms"], 6, 1)), ord(substr($current_file["parms"], 7, 1)), ord(substr($current_file["parms"], 8, 1)), ord(substr($current_file["parms"], 9, 1)));
		break;
	case 11:
		$current_file["ignored"] = substr($current_file["parms"], 0, 6);
		$current_file["dir"] = ord(substr($current_file["parms"], 6, 1));
		$current_file["firstbyte"] = le_to_int(ord(substr($current_file["parms"], 7, 1)), ord(substr($current_file["parms"], 8, 1)), ord(substr($current_file["parms"], 9, 1)), ord(substr($current_file["parms"], 10, 1)));
		break;
	default:
		echo "file name: '".$current_file["name"]."' not added!\n";
	}
	$current_file["firstfilebyte"] = $current_file["firstbyte"] + 9;
	//read the file header from the container
	$fdblock = "";
	for ($i = 0; $i < 9; $i++) {
		$fdblock .= getchar($fp, $current_file["firstbyte"] + $i);
	}
	//check the header block
	if (ord(substr($fdblock, 0, 1)) != 2) {
		echo "file ".$current_file["name"].": broken file header!\n";
	} else {
		$current_file["flen"] = le_to_int(ord(substr($fdblock, 1, 1)), ord(substr($fdblock, 2, 1)), ord(substr($fdblock, 3, 1)), ord(substr($fdblock, 4, 1)));
		$current_file["fhdr"] = substr($fdblock, 5, 4);
	}
	//write the file
	if ($unpack) {
		$ffp = fopen($unpackdir."/".$dirs[$current_file["dir"]].$current_file["name"], "wb");
		for ($i = 0; $i < $current_file["flen"]; $i++) {

			fwrite($ffp, getchar($fp, $current_file["firstfilebyte"] + $i));
		}
		fclose($ffp);
	}
	//get the last byte of the file
	$lastfb = getchar($fp, $current_file["firstfilebyte"] + $current_file["flen"] - 1);
	//get the end marker (duplicate the last byte of the file)
	$padfb = getchar($fp, $current_file["firstfilebyte"] + $current_file["flen"]);

	echo "file '".$current_file["name"]."', fnlen ".strlen($current_file["name"]).", directory '".$dirs[$current_file["dir"]]."', flen 0x".dechex($current_file["flen"]).", begin header at 0x".dechex($current_file["firstbyte"]).", begin file at 0x".dechex($current_file["firstfilebyte"]).", ignored '";
	for ($i = 0; $i < strlen($current_file["ignored"]); $i++)
		echo str_pad(dechex(ord(substr($current_file["ignored"], $i, 1))), 2, 0, STR_PAD_LEFT)." ";
	echo "', headerbytes '";
	for ($i = 0; $i < strlen($current_file["fhdr"]); $i++)
		echo str_pad(dechex(ord(substr($current_file["fhdr"], $i, 1))), 2, 0, STR_PAD_LEFT)." ";
	echo "', last byte '".str_pad(dechex(ord($lastfb)), 2, 0, STR_PAD_LEFT)."', after-last byte '".str_pad(dechex(ord($padfb)), 2, 0, STR_PAD_LEFT)."'\n";
	$files[$id] = $current_file;
	//	echo "directory id is ".$current_file["dir"]." (".$dirs[$current_file["dir"]]."), begin is at 0x".dechex($current_file["firstbyte"])."\n";
}
if ($fcnt != sizeof($files))
	echo "warning: number of files differs from the actually present amount\n";
if ($files[sizeof($files) - 1]["firstfilebyte"] + $files[sizeof($files) - 1]["flen"] + 1 != filesize($f))
	echo "warning: trailing bytes after file end\n";

//print_r($files);
?>